package progetto.misure;

import grafi.Arco;
import grafi.Grafo;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class Misuratore
{

	protected Grafo<Arco> grafo;

	// DA DECIDERE SE ArrayList
	protected MatriceCammini camminiMinimi = null;
	protected double[] between = null;
	protected double[] boundedBetween = null;


	// private ArrayList<Double> pageRank = null;
	// private ArrayList<Double> hits = null;
	// private ArrayList<Double> between = null;
	// private ArrayList<Double> boundBetween = null;

	public Misuratore(Grafo<Arco> g)
	{
		this.grafo = g;
	}


	// ************************** PAGERANK ************************** //
	private double[] calcolaPageRank(double M[][], int nIterazioni)
	{
		int rM = M.length;
		double p[] = new double[rM];

		for (int k = 0; k < rM; k++)
			p[k] = 1 / (double) rM;

		boolean convergenza = false;
		for (int i = 1; i <= nIterazioni && !convergenza; i++)
		{
			// printArray(p);
			double pPrec[] = p;
			p = calcolaP1(M, pPrec);
			double toll = calcolaTolleranzaMedia(p, pPrec);
			//System.out.println("Tolleranza media iterazione n° " + i + ": " + toll);
			convergenza = toll < 1.0E-9;
		}
		return p;
	}


	private double[] calcolaP1(double M[][], double p[])
	{
		int rM = M.length;
		int cM = M[0].length;
		double alpha = 0.8;

		if (rM != cM)
			throw new IllegalArgumentException();
		double v1[] = new double[rM];

		for (int k = 0; k < rM; k++)
			v1[k] = (1 - alpha) / rM;

		double vb[] = MisureUtil.prodottoScalareVettore(MisureUtil.prodottoMatriceVettore(M, p), alpha);

		double P1[] = MisureUtil.sommaVettori(v1, vb);

		return P1;
	}


	private double calcolaTolleranzaMedia(double[] p1, double[] p2)
	{
		double toll = 0.0;
		for (int i = 0; i < p1.length; i++)
			toll = toll + Math.abs(p1[i] - p2[i]);
		return toll / (double) p1.length;
	}


	// ************************** HITS ************************** //
	private double[] calcolaHits(double M[][], int nIterazioni)
	{
		int n = M.length;
		double h[] = new double[n];

		for (int k = 0; k < n; k++)
			h[k] = 1;
		double a[] = new double[n];
		double[][] MT = MisureUtil.matriceTrasposta(M);
		boolean convergenza = false;
		for (int i = 1; i <= nIterazioni && !convergenza; i++)
		{
			// printArray(p);
			double aPrec[] = a;
			a = calcolaA1(MT, aPrec, h);

			double hPrec[] = h;
			h = calcolaH1(M, hPrec, a);

			double tollA = calcolaTolleranzaMedia(a, aPrec);
			double tollH = calcolaTolleranzaMedia(h, hPrec);

			convergenza = Math.max(tollA, tollH) < 1.0E-9;
		}
		return a;
	}


	private double[] calcolaH1(double L[][], double h[], double a[])
	{
		int rL = L.length;
		int cL = L[0].length;

		if (rL != cL)
			throw new IllegalArgumentException();

		double h1[] = MisureUtil.prodottoMatriceVettore(L, a);

		scala(h1);

		return h1;
	}


	private double[] calcolaA1(double LT[][], double a[], double h[])
	{
		double a1[] = MisureUtil.prodottoMatriceVettore(LT, h);
		scala(a1);

		return a1;
	}


	private void scala(double[] a)
	{
		double somma = 0;
		for (int i = 0; i < a.length; i++)
			somma += a[i];
		for (int j = 0; j < a.length; j++)
			a[j] = a[j] / somma;

	}


	// ************************** ADIACENZA - TRANSIZIONE **************************
	private double[][] getMatriceAdiacenza(Grafo<Arco> g)
	{
		int n = g.getN();

		double[][] adiacenza = new double[n][n];
		for (int i = 0; i < n; i++)
		{
			for (int j = 0; j < n; j++)
			{
				adiacenza[i][j] = 0;
			}
		}
		Iterator<Arco> it = g.archi();
		while (it.hasNext())
		{
			Arco a = it.next();
			adiacenza[a.getIn()][a.getFin()] = 1;
		}

		return adiacenza;

	}


	private double[][] getMatriceTransizione(double[][] x)
	{
		int n = x.length;
		int vettoreGradi[] = new int[n];
		for (int i = 0; i < n; i++)
		{
			vettoreGradi[i] = 0;
			for (int j = 0; j < n; j++)
			{

				if (x[i][j] != 0)
				{

					vettoreGradi[i]++;
				}
			}
		}

		double[][] transizione = new double[n][n];
		for (int i = 0; i < n; i++)
		{
			for (int j = 0; j < n; j++)
			{
				if (x[i][j] != 0.0)
				{

					transizione[j][i] = 1 / (double) vettoreGradi[i];
				}
			}
		}

		return transizione;

	}


	// CALCOLA CAMMINI MINIMI
	private MatriceCammini camminiMinimi()
	{
		int n = grafo.getN();
		{
			// STRUTTURA
			MatriceCammini matriceCammini = new MatriceCammini(n);
			double[][] matriceDiAdiacenza = getMatriceAdiacenza(grafo);

			for (int i = 0; i < n; i++)
			{
				for (int j = 0; j < n; j++)
				{
					if (i == j)
					{
						Cammino cammino = new Cammino();
						cammino.aggiungiNodo(i);
						Cammini cammini = new Cammini();
						cammini.addCammino(cammino);
						matriceCammini.setCammini(cammini, i, j);
					}
					else if (matriceDiAdiacenza[i][j] == 1.0)
					{
						Cammino cammino = new Cammino();
						cammino.aggiungiNodo(i);
						cammino.aggiungiNodo(j);
						Cammini cammini = new Cammini();
						cammini.addCammino(cammino);
						matriceCammini.setCammini(cammini, i, j);
					}
					else if (matriceDiAdiacenza[i][j] == 0.0)
					{
						Cammini cammini = new Cammini();
						matriceCammini.setCammini(cammini, i, j);
					}

				}
			}

			// STAMPA MATRICE CAMMINI INIZIALIZZATA
			// System.out.println("------ MATRICE CAMMINI INIZIALIZZATA ---------");
			// matriceCammini.stampa();
			for (int k = 0; k < n; k++)
			{
				//System.out.println("------------- K = " + k + "--------------------");
				for (int i = 0; i < n; i++)
					for (int j = 0; j < n; j++)
						if (i != j && i != k && k != j)
						{
							double costo_I_J = matriceCammini.getCammini(i, j).getCostoCammini();
							double costo_I_K = matriceCammini.getCammini(i, k).getCostoCammini();
							double costo_K_J = matriceCammini.getCammini(k, j).getCostoCammini();

							if (costo_I_J > costo_I_K + costo_K_J)
							{
								// System.out.println("Condizione trovata in i = " + i + " j = " + j + " k = " + k);
								// System.out.println("Cioè " + "[" + i + "," + j + "]>[" + i + "," + k + "]+[" + k + "," + j + "]");
								// System.out.println("Cioè " + matriceCammini.getCammini(i, j).getCostoCammini() + ">" + matriceCammini.getCammini(i, k).getCostoCammini() + "+" + matriceCammini.getCammini(k, j).getCostoCammini());
								// System.out.println("Cioè devo sostituire " + matriceCammini.getCammini(i, j) + " con " + matriceCammini.getCammini(i, k) + "+" + matriceCammini.getCammini(k, j));
								//
								// System.out.println("");
								Cammini cammini = Cammini.prodottoCartesiano(matriceCammini.getCammini(i, k), matriceCammini.getCammini(k, j));

								matriceCammini.setCammini(cammini, i, j);

							}
							if (costo_I_J == costo_I_K + costo_K_J && costo_I_J != Double.POSITIVE_INFINITY)
							{
								// System.out.println("Prima " + i + " " + j);
								//matriceCammini.stampa();
								matriceCammini.getCammini(i, j).addCammini(Cammini.prodottoCartesiano(matriceCammini.getCammini(i, k), matriceCammini.getCammini(k, j)));
								// System.out.println("Dopo");
								//matriceCammini.stampa();

							}
						}
				//matriceCammini.stampa();

			}
			camminiMinimi = matriceCammini;
			return matriceCammini;
		}
	}


	private double[] calcolaBetween()
	{
		if (between != null)
			return between;

		if (camminiMinimi == null)
			camminiMinimi();

		int n = camminiMinimi.getSizeMatrice();

		between = new double[n];

		double numeroTotaleCammini = 0.0; // Numero totale di cammini minimi da per i - j

		double numeroPresenzeNodoInCammini = 0.0; // Numero di cammini minimi da i - j in cui è presente t con t diverso da i e j diverso da
		double sommaTotale = 0.0;

		for (int t = 0; t < n; t++)
		{
			//System.out.println("------------- t = " + t + " --------------------");
			for (int i = 0; i < n; i++)
			{
				for (int j = 0; j < n; j++)
				{
					if (i != j && j != t && i != t && camminiMinimi.getCammini(i, j).getCostoCammini() > 1)
					{
						numeroTotaleCammini = (double) camminiMinimi.getCammini(i, j).getNumeroCammini();
						numeroPresenzeNodoInCammini = (double) camminiMinimi.getCammini(i, j).getNumeroPresenzeNodoInCammini(t);

						//System.out.println("Esamino " + t + " nella cella (" + i + " " + j + ") numeroTotaleCammini della cella = " + numeroTotaleCammini + " presenze = " + numeroPresenzeNodoInCammini);
						//System.out.println("");
						if (numeroTotaleCammini == 0.0 && numeroPresenzeNodoInCammini == 0.0)
						{
							//System.out.println("Faccio sommatotale + 0 sommaTotale prima = " + sommaTotale);
							sommaTotale = sommaTotale + 0.0;
						}
						else
						{
							//System.out.println("Esamino " + t + " nella cella (" + i + " " + j + ") numeroTotaleCammini della cella = " + numeroTotaleCammini + " presenze = " + numeroPresenzeNodoInCammini);
							sommaTotale = sommaTotale + numeroPresenzeNodoInCammini / numeroTotaleCammini;
							//System.out.println("Faccio sommatotale + " + numeroPresenzeNodoInCammini / numeroTotaleCammini );

						}
					}
				}
			}
			between[t] = sommaTotale;
			sommaTotale = 0.0;
		}
		System.out.println("VETTORE BETWEEN ");
		MisureUtil.printArray(between);
		return between;
	}


	private double[] calcolaBoundedBetween(double k)
	{

		if (boundedBetween != null)
			return boundedBetween;

		if (camminiMinimi == null)
			camminiMinimi();

		int n = camminiMinimi.getSizeMatrice();

		boundedBetween = new double[n];

		double numeroTotaleCammini = 0.0; // Numero totale di cammini minimi da per i - j

		double numeroPresenzeNodoInCammini = 0.0; // Numero di cammini minimi da i - j in cui è presente t con t diverso da i e j diverso da
		double sommaTotale = 0.0;

		for (int t = 0; t < n; t++)
		{
			//System.out.println("------------- t = " + t + " --------------------");
			for (int i = 0; i < n; i++)
			{
				for (int j = 0; j < n; j++)
				{
					if (i != j && j != t && i != t && camminiMinimi.getCammini(i, j).getCostoCammini() > 1 && camminiMinimi.getCammini(i, j).getCostoCammini() <= k)
					{
						numeroTotaleCammini = (double) camminiMinimi.getCammini(i, j).getNumeroCammini();
						numeroPresenzeNodoInCammini = (double) camminiMinimi.getCammini(i, j).getNumeroPresenzeNodoInCammini(t);

					//	System.out.println("Esamino " + t + " nella cella (" + i + " " + j + ") numeroTotaleCammini della cella = " + numeroTotaleCammini + " presenze = " + numeroPresenzeNodoInCammini);
					//System.out.println("");
						if (numeroTotaleCammini == 0.0 && numeroPresenzeNodoInCammini == 0.0)
						{
							//System.out.println("Faccio sommatotale + 0 sommaTotale prima = " + sommaTotale);
							sommaTotale = sommaTotale + 0.0;
						}
						else
						{
							//System.out.println("Esamino " + t + " nella cella (" + i + " " + j + ") numeroTotaleCammini della cella = " + numeroTotaleCammini + " presenze = " + numeroPresenzeNodoInCammini);
							sommaTotale = sommaTotale + numeroPresenzeNodoInCammini / numeroTotaleCammini;
							//System.out.println("Faccio sommatotale + " + numeroPresenzeNodoInCammini / numeroTotaleCammini );

						}
					}
				}
			}
			boundedBetween[t] = sommaTotale;
			sommaTotale = 0.0;
		}
		System.out.println("VETTORE boundedBetween ");
		MisureUtil.printArray(boundedBetween);
		return boundedBetween;
	}


	// ************************** METODI GET PUBBLICI **************************

	public Grafo<Arco> getGrafo()
	{
		return grafo;
	}


	public double[] getPageRank()
	{
		// MATRICE DI ADIACENZA
		double[][] matriceDiAdiacenza = getMatriceAdiacenza(grafo);
//		System.out.println("Matrice Di Adiacenza");
//		MisureUtil.printMatrice(matriceDiAdiacenza);
//		System.out.println("");

		// MATRICE DI TRANSIZIONE
		double[][] matriceDiTransizione = getMatriceTransizione(matriceDiAdiacenza);
//		System.out.println("Matrice Di Transizione");
//		MisureUtil.printMatrice(matriceDiTransizione);
//		System.out.println("");

		// CALCOLO DI PAGERANK
		double[] pageRank = calcolaPageRank(matriceDiTransizione, 100);
		System.out.println("Vettore Di PageRank");
		MisureUtil.printArray(pageRank);
		return pageRank;

	}


	public double[] getHits()
	{
		// MATRICE DI ADIACENZA
		double[][] matriceDiAdiacenza = getMatriceAdiacenza(grafo);
//		System.out.println("Matrice Di Adiacenza");
//		MisureUtil.printMatrice(matriceDiAdiacenza);
//		System.out.println("");

		// CALCOLO DI HITS
		double[] vectorHits = calcolaHits(matriceDiAdiacenza, 100);
		System.out.println("Vettore Di Hits");
		MisureUtil.printArray(vectorHits);
		return vectorHits;
	}


	// TODO
	public double[] getBetween()
	{
		// if(between==null)

		// MATRICE DEI CAMMINI MINIMI
		MatriceCammini oggettoCammini = camminiMinimi();
		Cammini[][] matriceCamminiMinimi = oggettoCammini.getMatrice();

		double[] between = calcolaBetween();
		return between;
	}


	// TODO
	public double[] getBoundedBetween(int k)
	{

		// MATRICE DEI CAMMINI MINIMI
		MatriceCammini oggettoCammini = camminiMinimi();
		Cammini[][] matriceCamminiMinimi = oggettoCammini.getMatrice();

		double[] between = calcolaBoundedBetween(k);
		return between;
	}

}
